﻿using Microsoft.Extensions.Options;
using StackExchange.Redis;
using System.Net;
using System.Text.Json;

namespace Shop.Core.Redis
{
    public class RedisHelper
    {
        //连接字符串
        private RedisConfig _options;

        private readonly Lazy<ConnectionMultiplexer> _connectionMultiplexer;

        public RedisHelper(IOptions<RedisConfig> config)
        {
            _options = config.Value;
            _connectionMultiplexer = new Lazy<ConnectionMultiplexer>(CreateConnectionMultiplexer);
        }

        /// <summary>
        /// Creates the connection multiplexer.
        /// </summary>
        /// <returns>The connection multiplexer.</returns>
        private ConnectionMultiplexer CreateConnectionMultiplexer()
        {
            return ConnectionMultiplexer.Connect(_options.ConnectionString);
        }


        /// <summary>
        /// 获取数据库
        /// </summary>
        /// <param name="configName"></param>
        /// <param name="db">默认为0：优先代码的db配置，其次config中的配置</param>
        /// <returns></returns>
        public IDatabase GetDatabase()
        {
            return _connectionMultiplexer.Value.GetDatabase();
        }

        /// <summary>
        /// Gets the server list.
        /// </summary>
        /// <returns>The server list.</returns>
        public IEnumerable<IServer> GetServerList()
        {
            var endpoints = GetMastersServersEndpoints();

            foreach (var endpoint in endpoints)
            {
                yield return _connectionMultiplexer.Value.GetServer(endpoint);
            }
        }

        /// <summary>
        /// Gets the masters servers endpoints.
        /// </summary>
        private List<EndPoint> GetMastersServersEndpoints()
        {
            var masters = new List<EndPoint>();
            foreach (var ep in _connectionMultiplexer.Value.GetEndPoints())
            {
                var server = _connectionMultiplexer.Value.GetServer(ep);
                if (server.IsConnected)
                {
                    //Cluster
                    if (server.ServerType == ServerType.Cluster)
                    {
                        masters.AddRange(server.ClusterConfiguration.Nodes.Where(n => !n.IsReplica).Select(n => n.EndPoint));
                        break;
                    }
                    // Single , Master-Slave
                    if (server.ServerType == ServerType.Standalone && !server.IsReplica)
                    {
                        masters.Add(ep);
                        break;
                    }
                }
            }
            return masters;
        }

        public async Task<(bool Success, string LockValue)> LockAsync(string cacheKey, int timeoutSeconds = 5)
        {
            var lockKey = GetLockKey(cacheKey);
            var lockValue = Guid.NewGuid().ToString();
            var timeoutMilliseconds = timeoutSeconds * 1000;
            var expiration = TimeSpan.FromMilliseconds(timeoutMilliseconds);
            bool flag = await this.GetDatabase().StringSetAsync(lockKey, lockValue, expiration, When.NotExists,CommandFlags.None);
            return (flag, flag ? lockValue : null);
        }

        public async Task<string> GetStringAsync(string cacheKey)
            =>  await this.GetDatabase().StringGetAsync(cacheKey);
        public async Task<T?> GetAsync<T>(string cacheKey)
        {
            var data = await this.GetDatabase().StringGetAsync(cacheKey);
            if (string.IsNullOrEmpty(data))
                return default(T);
            return JsonSerializer.Deserialize<T>(data);
        }

        public async Task<bool> SetStringAsyn(string cacheKey, string data)
            => await this.GetDatabase().StringSetAsync(cacheKey, new RedisValue(data));
        public async Task<bool> SetAsync<T>(string cacheKey, T data)
        {
            var value = JsonSerializer.Serialize(data);
            return await this.GetDatabase().StringSetAsync(cacheKey, new RedisValue(value));
        }

        public async Task<bool> SafedUnLockAsync(string cacheKey, string lockValue)
        {
            var lockKey = GetLockKey(cacheKey);

            var script = @"local invalue = @value
                                    local currvalue = redis.call('get',@key)
                                    if(invalue==currvalue) then redis.call('del',@key)
                                        return 1
                                    else
                                        return 0
                                    end";
            var parameters = new { key = lockKey, value = lockValue };
            var prepared = LuaScript.Prepare(script);
            var result = (int)await this.GetDatabase().ScriptEvaluateAsync(prepared, parameters);
            return result == 1;
        }


        private static string GetLockKey(string cacheKey)
        {
            return $"adnc:locker:{cacheKey.Replace(":", "-")}";
        }
    }
}
